#include "ogl/gut/App.h"
#include "ogl/gut/types.h"
#include "wtclog/Logger.h"

using namespace Ogl::Gut;

namespace
{
    App* g_app = nullptr;
    void glfw_error_callback(int error, const char* description)
    {
        wtclog::info("Glfw Error %d: %s\n", error, description);
    }
    void glfw_key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
    {
        if (g_app)
        {
            g_app->OnKeyCallback(key, scancode, action, mode);
        }
    }
    void glfw_cursorpos_callback(GLFWwindow* window, double x, double y)
    {

        if (g_app)
        {
            g_app->OnCursorPos(x, y);
        }
    }
    void glfw_mouse_callback(GLFWwindow* window, int key, int action, int mode)
    {
        if (g_app)
        {
            g_app->OnMouse(key, action, mode);
        }
    }
    void glfw_frame_callback(GLFWwindow* window, int width, int height)
    {
        if (g_app)
        {
            g_app->OnFrameBufferSize(width, height);
        }
    }
    void glfw_monitor_callback(GLFWmonitor* window, int ev)
    {
        if (g_app)
        {
            g_app->OnMonitor(ev);
        }
    }
    void glfw_window_focus_callback(GLFWwindow* window, int e)
    {
        if (g_app)
        {
            g_app->OnFocus(e);
        }
    }
    void glfw_close_callback(GLFWwindow* window)
    {
        if (g_app)
        {
            g_app->OnClose();
        }
    }
    void glfw_joystick_callback(int joy, int event)
    {
        if (g_app)
        {
            g_app->OnJoystick(joy, event);
        }
    }
    void glfw_window_iconify_callback(GLFWwindow* window, int ev)
    {
        if (g_app)
        {
            g_app->OnWindowIconify(ev);
        }
    }
    void glfw_scroll_callback(GLFWwindow* window, double x, double y)
    {
        if (g_app)
        {
            g_app->OnScroll(x, y);
        }
    }
    void glfw_refresh_callback(GLFWwindow* window)
    {
        if (g_app)
        {
            g_app->OnRefresh();
        }
    }
    void glfw_pos_callback(GLFWwindow*, int x, int y)
    {
        if (g_app)
        {
            g_app->OnWindowPos(x, y);
        }
    }

    void glfw_drop_callback(GLFWwindow *window, int count, const char **paths)
    {
        if (g_app)
        {
            g_app->OnDrop(count, paths);
        }
    };
};

App::App(const Desc &desc):m_Desc(desc)
{
    g_app = this;
}

bool App::InitOpengl()
{
    if (!glfwInit())
    {
        wtclog::info("canont init glfw");
        return 0;
    }

    glfwSetErrorCallback(glfw_error_callback);

    // Decide GL+GLSL versions
#if defined(IMGUI_IMPL_OPENGL_ES2)
    // GL ES 2.0 + GLSL 100
    const char* glsl_version = "#version 100";
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
    glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
#elif defined(__APPLE__)
    // GL 3.2 + GLSL 150
    const char* glsl_version = "#version 150";
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);  // 3.2+ only
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);            // Required on Mac
#else
    // GL 3.0 + GLSL 130
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_SAMPLES, 4);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);  // 3.2+ only
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);            // 3.0+ only
#endif


    window = glfwCreateWindow(m_Desc.width, m_Desc.height, m_Desc.title, nullptr, nullptr);
    glfwSetWindowPos(window, m_Desc.posX, m_Desc.posY);

    if (!window)
    {
        wtclog::info("Failed to create GLFW window");
        glfwTerminate();
        return -1;
    }

    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, glfw_frame_callback);
    glfwSetKeyCallback(window, glfw_key_callback);
    glfwSetCursorPosCallback(window, glfw_cursorpos_callback);
    glfwSetMouseButtonCallback(window, glfw_mouse_callback);
    glfwSetWindowFocusCallback(window, glfw_window_focus_callback);
    glfwSetWindowCloseCallback(window, glfw_close_callback);
    glfwSetScrollCallback(window, glfw_scroll_callback);
    glfwSetWindowPosCallback(window, glfw_pos_callback);
    glfwSetDropCallback(window, glfw_drop_callback);

    glfwSetWindowIconifyCallback(window, glfw_window_iconify_callback);
    glfwSetJoystickCallback(glfw_joystick_callback);
    glfwSetMonitorCallback(glfw_monitor_callback);
    // glfwSetClipboardString(window, "hello world");
    glfwSetWindowRefreshCallback(window, glfw_refresh_callback);

    glfwSwapInterval(1); // Enable vsync

#ifdef _WIN32
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return 0;
    }
#endif
    OnFrameBufferSize(m_Desc.width, m_Desc.height);
    
    m_Timer.OnStart();
    
    return true;
};

void App::SetClipboard(const char *text) {
    glfwSetClipboardString(window, text);
    
};
const float App::GetAspect()
{
    if (m_Desc.height != 0)
    {
        return (float)m_Desc.width / (float)m_Desc.height;
    }
    return 1.0f;
};

void App::SetFullScreen()
{
    glfwSetWindowMonitor(window, glfwGetPrimaryMonitor(), 0, 0, m_Desc.width, m_Desc.height, GLFW_DONT_CARE);
    m_Desc.m_FullScreen= true;
}

void App::SetWindowMode()
{
    glfwSetWindowMonitor(window, NULL, m_Desc.posX, m_Desc.posY, m_Desc.width, m_Desc.height, GLFW_DONT_CARE);
    m_Desc.m_FullScreen = false;
}

void App::CloseWindow()
{
    glfwSetWindowShouldClose(window, true);
}

bool App::Init()
{
    if(!InitOpengl())
    {
        return false;
    }

    return true;
};


void App::Execute()
{
    // ImGui_ImplOpenGL2_Init();

    while (!glfwWindowShouldClose(window))
    {
        glfwPollEvents();

        //if (m_Desc.m_Focused)
        {
            OnFrameRender();
            OnTick();
            OnAfterUpdate();

        }
      
        glfwSwapBuffers(window);
    }

}


void App::OnCursorPos(double x, double y)
{
    inputSystem.OnCursorPos(x, y);
}

void App::OnMouse(int key, int action, int mode)
{
    if (action == GLFW_REPEAT)
    {
        //LOGI("repeast a mouse event");
    }
    if (action == GLFW_PRESS)
    {
        //LOGI("press e mosue event");
    }
    inputSystem.OnMouse(key, action, mode);
}

void App::OnKeyCallback(int key, int scancode, int action, int mode)
{
    inputSystem.OnKeyCallback(key, scancode, action, mode);
}

void App::OnJoystick(int joy, int event)
{
    inputSystem.OnJoystick(joy, event);
}

void App::OnWindowIconify(int ev)
{


}

void App::OnRefresh()
{
}

void App::OnWindowPos(int x, int y)
{
    if (!m_Desc.fullScreen)
    {
        m_Desc.posX = x;
        m_Desc.posY = y;
    }
}

void App::OnTick()
{
    const char* name = glfwGetJoystickName(GLFW_JOYSTICK_1);

    {
        auto& joystick = inputSystem.joystick;

        int axisCount;
        const float* axis = glfwGetJoystickAxes(GLFW_JOYSTICK_1, &axisCount);
        joystick.OnAxis(axis, axisCount);

        int buttonCount;
        const unsigned char* btns = glfwGetJoystickButtons(GLFW_JOYSTICK_1, &buttonCount);
        joystick.OnButton(btns, buttonCount);
    }

    m_Timer.OnTick();


}

void App::OnAfterUpdate()
{
    inputSystem.Clear();

}

App::~App()
{

    OnDestroy();
}

void App::OnDestroy()
{
    glfwDestroyWindow(window);
    glfwTerminate();
}

void App::OnClose()
{

};

void App::OnScroll(double x, double y)
{
    inputSystem.OnScroll(x, y);
}

void App::OnFrameRender()
{
    glClearColor(0.5f, 1.0f, 0.52f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

}
void App::OnFrameBufferSize(int w, int h) {

    if (w == 0 || h == 0)
    {
        m_Desc.m_Minialized = true;
        return;
    }
    m_Desc.width = w;
    m_Desc.height = h;
    glViewport(0, 0, w, h);
};

void App::OnDrop(int count, const char **paths){

};
void App::OnFocus(int e) {
    m_Desc.m_Focused = e;
};

void App::OnMonitor(int e)
{
}